 /*******************************************************************************
  * Copyright (c) 2005 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.internal.resources;

 import java.util.*;
 import org.eclipse.core.internal.utils.Cache;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.ProjectScope;
 import org.eclipse.core.runtime.*;
 import org.eclipse.core.runtime.content.*;
 import org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy;
 import org.eclipse.core.runtime.preferences.*;
 import org.osgi.service.prefs.BackingStoreException;
 import org.osgi.service.prefs.Preferences;

 /**
  * Manages project-specific content type behavior.
  *
  * @see ContentDescriptionManager
  * @see IContentTypeManager.ISelectionPolicy
  * @since 3.1
  */
 public class ProjectContentTypes {

     /**
      * A project-aware content type selection policy.
      * This class is also a dynamic scope context that will delegate to either
      * project or instance scope depending on whether project specific settings were enabled
      * for the project in question.
      */
     private class ProjectContentTypeSelectionPolicy implements ISelectionPolicy, IScopeContext {
         // corresponding project
 private Project project;
         // cached project scope
 private IScopeContext projectScope;

         public ProjectContentTypeSelectionPolicy(Project project) {
             this.project = project;
             this.projectScope = new ProjectScope(project);
         }

         /* (non-Javadoc)
          * @see java.lang.Object#equals(java.lang.Object)
          */
         public boolean equals(Object obj) {
             if (this == obj)
                 return true;
             if (!(obj instanceof IScopeContext))
                 return false;
             IScopeContext other = (IScopeContext) obj;
             if (!getName().equals(other.getName()))
                 return false;
             IPath location = getLocation();
             return location == null ? other.getLocation() == null : location.equals(other.getLocation());
         }

         private IScopeContext getDelegate() {
             if (!usesContentTypePreferences(project.getName()))
                 return ProjectContentTypes.INSTANCE_SCOPE;
             return projectScope;
         }

         public IPath getLocation() {
             return getDelegate().getLocation();
         }

         public String getName() {
             return getDelegate().getName();
         }

         public IEclipsePreferences getNode(String qualifier) {
             return getDelegate().getNode(qualifier);
         }

         /* (non-Javadoc)
          * @see java.lang.Object#hashCode()
          */
         public int hashCode() {
             return getName().hashCode();
         }

         public IContentType[] select(IContentType[] candidates, boolean fileName, boolean content) {
             return ProjectContentTypes.this.select(project, candidates, fileName, content);
         }
     }

     private static final String CONTENT_TYPE_PREF_NODE = "content-types"; //$NON-NLS-1$

     static final InstanceScope INSTANCE_SCOPE = new InstanceScope();

     private static final String PREF_LOCAL_CONTENT_TYPE_SETTINGS = "enabled"; //$NON-NLS-1$
 private static final Preferences PROJECT_SCOPE = Platform.getPreferencesService().getRootNode().node(ProjectScope.SCOPE);
     private Cache contentTypesPerProject;
     private Workspace workspace;

     static boolean usesContentTypePreferences(String projectName) {
         try {
             // be careful looking up for our node so not to create any nodes as side effect
 Preferences node = PROJECT_SCOPE;
             //TODO once bug 90500 is fixed, should be simpler
 // for now, take the long way
 if (!node.nodeExists(projectName))
                 return false;
             node = node.node(projectName);
             if (!node.nodeExists(Platform.PI_RUNTIME))
                 return false;
             node = node.node(Platform.PI_RUNTIME);
             if (!node.nodeExists(CONTENT_TYPE_PREF_NODE))
                 return false;
             node = node.node(CONTENT_TYPE_PREF_NODE);
             return node.getBoolean(PREF_LOCAL_CONTENT_TYPE_SETTINGS, false);
         } catch (BackingStoreException e) {
             // exception treated when retrieving the project preferences
 }
         return false;
     }

     public ProjectContentTypes(Workspace workspace) {
         this.workspace = workspace;
         // keep cache small
 this.contentTypesPerProject = new Cache(5, 30, 0.4);
     }

     /**
      * Collect content types associated to the natures configured for the given project.
      */
     private Set collectAssociatedContentTypes(Project project) {
         String [] enabledNatures = workspace.getNatureManager().getEnabledNatures(project);
         if (enabledNatures.length == 0)
             return Collections.EMPTY_SET;
         Set related = new HashSet(enabledNatures.length);
         for (int i = 0; i < enabledNatures.length; i++) {
             ProjectNatureDescriptor descriptor = (ProjectNatureDescriptor) workspace.getNatureDescriptor(enabledNatures[i]);
             if (descriptor == null)
                 // no descriptor found for the nature, skip it
 continue;
             String [] natureContentTypes = descriptor.getContentTypeIds();
             for (int j = 0; j < natureContentTypes.length; j++)
                 // collect associate content types
 related.add(natureContentTypes[j]);
         }
         return related;
     }

     public void contentTypePreferencesChanged(IProject project) {
         final ProjectInfo info = (ProjectInfo) ((Project) project).getResourceInfo(false, false);
         if (info != null)
             info.setMatcher(null);
     }

     /**
      * Creates a content type matcher for the given project. Takes natures and user settings into account.
      */
     private IContentTypeMatcher createMatcher(Project project) {
         ProjectContentTypeSelectionPolicy projectContentTypeSelectionPolicy = new ProjectContentTypeSelectionPolicy(project);
         return Platform.getContentTypeManager().getMatcher(projectContentTypeSelectionPolicy, projectContentTypeSelectionPolicy);
     }

     private Set getAssociatedContentTypes(Project project) {
         final ResourceInfo info = project.getResourceInfo(false, false);
         if (info == null)
             // the project has been deleted
 return null;
         final String projectName = project.getName();
         synchronized (contentTypesPerProject) {
             Cache.Entry entry = contentTypesPerProject.getEntry(projectName);
             if (entry != null)
                 // we have an entry...
 if (entry.getTimestamp() == info.getContentId())
                     // ...and it is not stale, so just return it
 return (Set) entry.getCached();
             // no cached information found, have to collect associated content types
 Set result = collectAssociatedContentTypes(project);
             if (entry == null)
                 // there was no entry before - create one
 entry = contentTypesPerProject.addEntry(projectName, result, info.getContentId());
             else {
                 // just update the existing entry
 entry.setTimestamp(info.getContentId());
                 entry.setCached(result);
             }
             return result;
         }
     }

     public IContentTypeMatcher getMatcherFor(Project project) throws CoreException {
         ProjectInfo info = (ProjectInfo) project.getResourceInfo(false, false);
         //fail if project has been deleted concurrently
 if (info == null)
             project.checkAccessible(project.getFlags(info));
         IContentTypeMatcher matcher = info.getMatcher();
         if (matcher != null)
             return matcher;
         matcher = createMatcher(project);
         info.setMatcher(matcher);
         return matcher;
     }

     /**
      * Implements project specific, nature-based selection policy. No content types are vetoed.
      *
      * The criteria for this policy is as follows:
      * <ol>
      * <li>associated content types should appear before non-associated content types</li>
      * <li>otherwise, relative ordering should be preserved.</li>
      * </ol>
      *
      * @see IContentTypeManager.ISelectionPolicy
      */
     final IContentType[] select(Project project, IContentType[] candidates, boolean fileName, boolean content) {
         // since no vetoing is done here, don't go further if there is nothing to sort
 if (candidates.length < 2)
             return candidates;
         final Set associated = getAssociatedContentTypes(project);
         if (associated == null || associated.isEmpty())
             // project has no content types associated
 return candidates;
         int associatedCount = 0;
         for (int i = 0; i < candidates.length; i++)
             // is it an associated content type?
 if (associated.contains(candidates[i].getId())) {
                 // need to move it to the right spot (unless all types visited so far are associated as well)
 if (associatedCount < i) {
                     final IContentType promoted = candidates[i];
                     // move all non-associated content types before it one one position up...
 for (int j = i; j > associatedCount; j--)
                         candidates[j] = candidates[j - 1];
                     // ...so there is an empty spot for the content type we are promoting
 candidates[associatedCount] = promoted;
                 }
                 associatedCount++;
             }
         return candidates;
     }
 }
